/************************************************************
* DS_BINLOG.CPP
*
* Binary logs system
************************************************************/
#include <ap/settings/ds_settings.h>
#include "ap_comm.h"
#include "ds_binlog.h"
#ifdef ENABLE_BINARY_LOG
#include "ds_data_type_impl.hpp"

namespace binlog {

LOGGER *binLogPtr = 0;

static const DWORD MAGIC_START_1 = 0x01d5a074;
static const DWORD MAGIC_START_2 = 0x0d575c7e;
static const DWORD MAGIC_NET_TEMPLATE_1 = 0x017fe70c;
static const DWORD MAGIC_NET_TEMPLATE_2 = 0x169949a8;
static const DWORD MAGIC_ADD_MAS_1 = 0x2a01bbbc;
static const DWORD MAGIC_ADD_MAS_2 = 0x2787a3c2;

static const int MAX_TRANSMITTED_ARGS = 6;

/**************************************************************
* PACKER_BASE::PACKER_BASE()
*
**************************************************************/
PACKER_BASE::PACKER_BASE()
{
   ResetBase();
}

/**************************************************************
* PACKER_BASE::WriteCommand()
*
**************************************************************/
void PACKER_BASE::WriteCommand(ds::BIT_STREAM& dest, PACKER_BASE::BIN_LOG_COMMAND command)
{
   switch( command ) {
      case BIN_LOG_BEGIN_EVENT :
         return;
      case BIN_LOG_NEW_TEMPLATE :
         dest.Write(MAGIC_NET_TEMPLATE_1);
         dest.Write(MAGIC_NET_TEMPLATE_2);
         return;
      case BIN_LOG_START :
         dest.Write(MAGIC_START_1);
         dest.Write(MAGIC_START_2);
         return;
      case BIN_LOG_NEW_MAS :
         dest.Write(MAGIC_ADD_MAS_1);
         dest.Write(MAGIC_ADD_MAS_2);
         return;
   };
   STRONG_ASSERT(false);
}

/**************************************************************
* PACKER_BASE::ReadCommand()
*
**************************************************************/
PACKER_BASE::BIN_LOG_COMMAND PACKER_BASE::ReadCommand(ds::BIT_STREAM& stream)
{
   UINT pos = stream.GetPos();
   if ( BYTES_TO_BITS(stream.GetSize()) - pos < BYTES_TO_BITS(2*sizeof(DWORD))) {
      return BIN_LOG_BEGIN_EVENT;
   }

   DWORD magic1, magic2;
   stream.Read(magic1);
   stream.Read(magic2);

   if ( magic1 == MAGIC_NET_TEMPLATE_1 && magic2 == MAGIC_NET_TEMPLATE_2 ) {
      return BIN_LOG_NEW_TEMPLATE;
   }

   if ( magic1 == MAGIC_START_1 && magic2 == MAGIC_START_2 ) {
      return BIN_LOG_START;
   }

   if ( magic1 == MAGIC_ADD_MAS_1 && magic2 == MAGIC_ADD_MAS_2 ) {
      return BIN_LOG_NEW_MAS;
   }

   // No magic, rewind stream
   stream.SetPos(pos);
   return BIN_LOG_BEGIN_EVENT;
}

/**************************************************************
* PACKER_BASE::Reset()
*
**************************************************************/
void PACKER_BASE::ResetBase()
{
   m_maxEventId = 0;
   m_lastTime = 0;
   m_maxMASIndex = 1;
}

/**************************************************************
* PACKER_BASE::WriteClientId()
*
**************************************************************/
void PACKER_BASE::WriteClientId(ds::BIT_STREAM& stream, int cid)
{
   if ( cid > 0 ) {
      UINT tmp = cid;
      stream.WriteCompact(tmp, MAX_BIN_LOG_CLIENTID + 1);
   } else {
      stream.WriteCompact(MAX_BIN_LOG_CLIENTID, MAX_BIN_LOG_CLIENTID + 1);
   }
}

/**************************************************************
* PACKER_BASE::ReadClientId()
*
**************************************************************/
void PACKER_BASE::ReadClientId(ds::BIT_STREAM& stream, int& cid)
{
   UINT tmp;
   stream.ReadCompact(tmp, MAX_BIN_LOG_CLIENTID + 1);
   if ( tmp != MAX_BIN_LOG_CLIENTID ) {
      cid = tmp;
   } else {
      cid = -1;
   }
}

/**************************************************************
* PACKER_BASE::WriteVA()
*
**************************************************************/
void PACKER_BASE::WriteVA(ds::BIT_STREAM& stream, const char * fmt, va_list list)
{
   dsVECTOR<ARG> args;

   while (*fmt) {
      while (*fmt && *fmt != '%') fmt++;
      if (*fmt == 0) {
         break;
      }
      fmt++;
      while (*fmt && *fmt >= '0' && *fmt <= '9') fmt++;
      if (*fmt == 0) {
         ASSERT(0);
         break;
      }
      if (*fmt == '.') { 
         fmt++;
      }
      if (*fmt == 0) {
         ASSERT(0);
         break;
      }
      while (*fmt && *fmt >= '0' && *fmt <= '9') fmt++;
      if (*fmt == 0) {
         ASSERT(0);
         break;
      }

      ARG arg;
      arg.type = ARG_MAX;

      switch (*fmt) {
         case 'i':
         case 'x':
         case 'd':
            arg.type = ARG_INT;
            arg.v_int = va_arg(list, int);
            break;
         case 'f':
            arg.type = ARG_DOUBLE;
            arg.v_double = va_arg(list, double);
            break;
         case 's':
            arg.type = ARG_STR;
            arg.v_str = va_arg(list, const char*);
            break;
         case '%':
            fmt++;
            break;
      }

      if (arg.type != ARG_MAX) {
         args.PushBack(arg);
      }
   }

   STRONG_ASSERT(args.Length() <= MAX_TRANSMITTED_ARGS);
   stream.WriteCompact(args.Length(), 0, MAX_TRANSMITTED_ARGS + 1);

   for ( int i = 0; i < args.Length(); ++i ) {
      stream.WriteCompact(args[i].type, 0, ARG_MAX);
      switch(args[i].type) {
         case ARG_INT :
            stream.Write(args[i].v_int);
            break;
         case ARG_DOUBLE :
            stream.Write(args[i].v_double);
            break;
         case ARG_STR :
            stream.Write(dsSTRING(args[i].v_str));
            break;
         case ARG_MAX :
         default:
            STRONG_ASSERT(0);
            break;
      };
   }
}

/***************************************************************
* PrintfInsertInt ()
*
***************************************************************/
static void PrintfInsertInt(dsSTRING& printfBuf, int value, int width, char widthChar)
{
   char buf[16];
   sprintf(buf, "%i", value);
   if (width) {
      printfBuf.Insert(printfBuf.Length(), widthChar, max((int)(width - strlen(buf)), 0));
   }
   const char* pBuf = &buf[0];
   while (*pBuf) {
      printfBuf += *pBuf;
      pBuf++;
   }
   return;
}

/***************************************************************
* PrintfInsertInt ()
*
***************************************************************/
static void PrintfInsertHex(dsSTRING& printfBuf, int value, int width, char widthChar)
{
   char buf[16];
   sprintf(buf, "%x", (UINT)value);
   if (width) {
      printfBuf.Insert(printfBuf.Length(), widthChar, max((int)(width - strlen(buf)), 0));
   }
   const char* pBuf = &buf[0];
   while (*pBuf) {
      printfBuf += *pBuf;
      pBuf++;
   }
   return;
}


/***************************************************************
* PrintfInsertFloat ()
*
***************************************************************/
void PrintfInsertFloat(dsSTRING& printfBuf, float value, int width, char widthChar, int prec)
{
   int   i, j;

   int valueInt = (int)value;
   if (value < 0.f && valueInt == 0) {
      printfBuf += '-';
   }
   PrintfInsertInt(printfBuf, valueInt, width, widthChar);

   if (value < 0.0f) {
      value = -value;
   }

   printfBuf += '.';

   if (prec != -1) {
      for (i = 0, j = 1; i < prec; i++) {
         value -= (int)value;
         value *= 10.0f;
         j = (int)value;
         ASSERT(j >=0 && j <= 9);
         printfBuf += ('0' + j);
      }
   } else {
      while (value > 0.0000000000001f) {
         value -= (int)value;
         value *= 10.0f;
         j = (int)value;
         ASSERT(j >=0 && j <= 9);
         printfBuf += ('0' + j);
      }
   }

   return;
}

/**************************************************************
* PACKER_BASE::ReadVA()
*
**************************************************************/
void PACKER_BASE::ReadVA(ds::BIT_STREAM& stream, const char * fmt, dsSTRING& printfBuf)
{
   int count;
   stream.ReadCompact(count, 0, MAX_TRANSMITTED_ARGS + 1);
   static dsVECTOR<ARG> args;
   static dsVECTOR<dsSTRING> tmpStrings;
   args.Resize(count);
   tmpStrings.Resize(count); // to avoid resize during read

   for ( int i = 0; i < args.Length(); ++i ) {
      stream.ReadCompact((int&)args[i].type, 0, ARG_MAX);
      switch(args[i].type) {
         case ARG_INT :
            stream.Read(args[i].v_int);
            break;
         case ARG_DOUBLE :
            stream.Read(args[i].v_double);
            break;
         case ARG_STR :
            stream.Read(tmpStrings[i]);
            args[i].v_str = tmpStrings[i].CStr();
            break;
         case ARG_MAX :
         default:
            STRONG_ASSERT(0);
            break;
      };
   }

   printfBuf.Reserve(32);
   int width = 0;
   char widthChar = ' ';
   char fieldType;
   int prec = -1;
   int haveNumberedArg = 0;
   int idxArg = -1;
   static const char* const fieldsWithArg = "sSdifI";

   while (*fmt) {
      if (*fmt == '%') {
         fmt++;
         // read width
         int width = 0;
         char widthChar = ' ';
         if (*fmt == '0') {
            widthChar = '0';
            fmt++;
         } else {
            widthChar = ' ';
         }
         while (*fmt >= '0' && *fmt <= '9') {
            width = width * 10 + *fmt - '0';
            fmt++;
         }
         // read precision
         prec = -1;
         if (*fmt == '.') {
            fmt++;
            prec = 0;
            while (*fmt >= '0' && *fmt <= '9') {
               prec = prec * 10 + *fmt - '0';
               fmt++;
            }
         }
         // read type
         if (*fmt == 0) {
            printfBuf += "<UNEXPECTED EOL>";
            break;
         }
         fieldType = *fmt;
         fmt++;
         // read argument if needed
         if (strchr(fieldsWithArg, fieldType)) {
            if (haveNumberedArg == 2 ) {
               // no numbered arguments (have some unnumbered before)
               idxArg++;
            } else if (*fmt < '1' || *fmt > '9') {
               // unnumbered argument found
               if (haveNumberedArg == 1) {
                  printfBuf += "<UNNUMBERED ARG>";
                  continue;
               } else {
                  haveNumberedArg = 2;
                  idxArg++;
               }
            } else {
               // numbered argument found
               haveNumberedArg = 1;
               idxArg = 0;
               while (*fmt >= '0' && *fmt <= '9') {
                  idxArg = idxArg * 10 + *fmt - '0';
                  fmt++;
               }
               idxArg--;
               if (idxArg < 0 || idxArg >= args.Length()) {
                  ASSERT(0);
                  printfBuf += "<INVALID ARG IDX>";
               }
            }
         }
         // process field
         switch (fieldType) {
            case '%':                        // %%: '%' char
               printfBuf += '%';
               break;
            case 't':                        // %[int]t add spaces to make string length == [int]
               printfBuf.Insert(printfBuf.Length(), '%', width - printfBuf.Length());
               break;   
            case 's':                        // %s: unicode string
               if (args[idxArg].type == ARG_STR) {
                  printfBuf += (const char*)args[idxArg].v_str;
                  if (width) {
                     printfBuf.Insert(printfBuf.Length(), widthChar, max((int)(width - strlen(args[idxArg].v_str)), 0));
                  }
               } else {
                  ASSERT(0);
                  printfBuf += "<INVALID ARG TYPE>";
               }
               break;
            case 'i':
            case 'd':
               if (args[idxArg].type == ARG_INT) {
                  PrintfInsertInt(printfBuf, args[idxArg].v_int, width, widthChar);
               } else {
                  ASSERT(0);
                  printfBuf += "<INVALID ARG TYPE>";
               }
               break;
            case 'x':
               if (args[idxArg].type == ARG_INT) {
                  PrintfInsertHex(printfBuf, args[idxArg].v_int, width, widthChar);
               } else {
                  ASSERT(0);
                  printfBuf += "<INVALID ARG TYPE>";
               }
               break;
            case 'f':
               if (args[idxArg].type == ARG_DOUBLE) {
                  PrintfInsertFloat(printfBuf, (float)args[idxArg].v_double, width, widthChar, prec);
               } else {
                  ASSERT(0);
                  printfBuf += "<INVALID ARG TYPE>";
               }
               break;
            default:
               printfBuf += "<INVALID FIELD>";
               break;
         }
      } else {
         printfBuf += *fmt;
         fmt++;
      }
   }
}

/**************************************************************
* WRITER::WRITER()
*
**************************************************************/
WRITER::WRITER() 
   : m_stream(ds::BIT_STREAM::WRITE)
{
#ifdef _AP_BIG_ENDIAN
   m_stream.SetReordering(true);
#else
   m_stream.SetReordering(false);
#endif
   Reset();
}

/**************************************************************
* WRITER::Write()
*
**************************************************************/
void WRITER::Write(int index, const char* eventName, va_list list)
{
   STRONG_ASSERT(index!=UNKNOWN_MAS);

   // Write first message
   if ( !m_firstMessageSend ) {
      WriteCommand(m_stream, BIN_LOG_START);
      m_stream.Write(osGetTime());
      m_lastTime = osGetTime();
      m_firstMessageSend = true;
   }

   // Write event name code if new
   UINT eventId = 0;
   ds::MAP<dsSTRING, UINT>::CONST_ITERATOR found = m_events.Find(eventName);
   if ( !found.IsDone() ) {
      eventId = *found;
   } else {
      eventId = m_maxEventId++;
      m_events.Insert(eventName, eventId);
      WriteCommand(m_stream, BIN_LOG_NEW_TEMPLATE);
      m_stream.Write(eventId);
      m_stream.Write(dsSTRING(eventName));
   }

   // Write MAS code if unknown
   ds::MAP< int, dsPAIR<net::MACHINE_ID, net::SESSION_ID> >::CONST_ITERATOR foundMas = m_knownMAS.Find(index);
   if ( foundMas.IsDone() ) {
      net::MACHINE_ID mid;
      net::SESSION_ID sessionId;
      apResolveMASIndex(index, mid, sessionId);
      m_knownMAS.Insert(index, dsMakePair(mid, sessionId) );
      WriteCommand(m_stream, BIN_LOG_NEW_MAS);
      m_stream.Write(index);
      m_stream.Write(mid);
      m_stream.Write(sessionId.ToStringDbg());
      m_maxMASIndex = max(m_maxMASIndex, index);
   }

   // Begin write event data
   WriteCommand(m_stream, BIN_LOG_BEGIN_EVENT);

   // Write eventId
   m_stream.WriteCompact(eventId, m_maxEventId);

   // Write MAS
   m_stream.WriteCompact(index, m_maxMASIndex);

   // Write timeStamp
   UINT delta = osGetTime() - m_lastTime;
   if ( delta < 255 ) {
      m_stream.Write(bool(true));
      m_stream.Write(BYTE(delta));
   } else {
      m_stream.Write(bool(false));
      m_stream.Write(osGetTime());
   }
   m_lastTime = osGetTime();

   // Write value argument list
   WriteVA(m_stream, eventName, list);
}

/**************************************************************
* WRITER::Reset()
*
**************************************************************/
void WRITER::Reset()
{
   m_knownMAS.Clear();
   PACKER_BASE::ResetBase();
   m_stream.Clear();
   m_events.Clear();
   m_firstMessageSend = false;
}

/**************************************************************
* READER::Reset()
*
**************************************************************/
void READER::Reset()
{
   m_knownMAS.Clear();
   PACKER_BASE::ResetBase();
   m_eventsTemplates.Clear();
}

/**************************************************************
* READER::Read()
*
**************************************************************/
void READER::Read(ds::BIT_STREAM& stream, UINT bytesToRead, dsVECTOR<BIN_LOG_EVENT>& events)
{
   UINT startPos = stream.GetPos();
   while ( BYTES_TO_BITS(bytesToRead) - (stream.GetPos() - startPos) > 8 ) {
      BIN_LOG_COMMAND command = ReadCommand(stream);
      switch (command) {
         case BIN_LOG_BEGIN_EVENT :
            {
               BIN_LOG_EVENT ev;
               ReadEvent(stream, ev);
               events.PushBack(ev);
               break;
            }
         case BIN_LOG_NEW_TEMPLATE :
            ReadEventTemplate(stream);
            break;
         case BIN_LOG_NEW_MAS :
            ReadNewMAS(stream);
            break;
         case BIN_LOG_START :
            Reset();
            stream.Read(m_lastTime);
            break;
      };
   }
}

/**************************************************************
* READER::ReadEvent()
*
**************************************************************/
void READER::ReadEvent(ds::BIT_STREAM& stream, BIN_LOG_EVENT& ev)
{
   bool signalBit = false;

   // Read eventId
   UINT eventId;
   stream.ReadCompact(eventId, m_maxEventId);
   ds::MAP<UINT, dsSTRING>::CONST_ITERATOR foundTemplate = m_eventsTemplates.Find(eventId);
   STRONG_ASSERT(!foundTemplate.IsDone());

   // Read MAS
   UINT mas;
   stream.ReadCompact(mas, m_maxMASIndex);
   ds::MAP< int, dsPAIR<net::MACHINE_ID, dsSTRING> >::CONST_ITERATOR foundMAS = m_knownMAS.Find(mas);
   STRONG_ASSERT(!foundMAS.IsDone());
   ev.mid = (*foundMAS).key;
   ev.sessionId = (*foundMAS).val;

   // Read timeStamp
   stream.Read(signalBit);
   if ( signalBit ) {
      BYTE delta = 0;
      stream.Read(delta);
      ev.timeStamp = m_lastTime + delta;
   } else {
      stream.Read(ev.timeStamp);
   }
   m_lastTime = ev.timeStamp;

   ReadVA(stream, foundTemplate->CStr(), ev.eventStr);
}

/**************************************************************
* READER::ReadEventTemplate()
*
**************************************************************/
void READER::ReadEventTemplate(ds::BIT_STREAM& stream)
{
   int newId;
   dsSTRING name;
   stream.Read(newId);
   stream.Read(name);
   m_eventsTemplates.Insert(newId, name);
   m_maxEventId = newId + 1;
}

/**************************************************************
* READER::ReadNewMAS()
*
**************************************************************/
void READER::ReadNewMAS(ds::BIT_STREAM& stream)
{
   int mas;
   net::MACHINE_ID mid;
   dsSTRING sessionId;
   stream.Read(mas);
   stream.Read(mid);
   stream.Read(sessionId);
   STRONG_ASSERT(m_knownMAS.Find(mas).IsDone());
   m_knownMAS.Insert(mas, dsMakePair(mid, sessionId));
   m_maxMASIndex = max(m_maxMASIndex, mas);
}

} // namespace binlog

static dsVECTOR< dsPAIR<net::MACHINE_ID, net::SESSION_ID> > gs_masData;
struct TMP_STATIC_REGISTRATOR {
   TMP_STATIC_REGISTRATOR()
   {
      STRONG_ASSERT(gs_masData.Length()==0);
      // Insert EMPTY_MAS
      net::MACHINE_ID mid;
      binlog::SetSpecialMachineId(mid);
      gs_masData.PushBack(dsMakePair(mid, net::EMPTY_SESSION_ID));
   }
};
static TMP_STATIC_REGISTRATOR gs_tmpStaticRegistrator;

/**************************************************************
* apGetMASIndex()
*
**************************************************************/
MAS_INDEX apGetMASIndex(const net::MACHINE_ID& mid, const net::SESSION_ID &sessId)
{
   for ( int i = 0; i < gs_masData.Length(); ++i ) {
      if ( gs_masData[i].key == mid && gs_masData[i].val == sessId ) {
         return i;
      }
   }
   int res = gs_masData.Length();
   gs_masData.PushBack( dsMakePair(mid, sessId) );
   return res;
}

/**************************************************************
* apResolveMASIndex()
*
**************************************************************/
void apResolveMASIndex(int ind, net::MACHINE_ID& mid, net::SESSION_ID& sessId)
{
   STRONG_ASSERT(ind < gs_masData.Length());
   mid = gs_masData[ind].key;
   sessId = gs_masData[ind].val;
}
#endif

namespace binlog {
static const BYTE fibanacciSeq[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233};

void SetSpecialMachineId(net::MACHINE_ID& mid)
{
   mid.Clear();
   for ( int i = 0; i < min(_NElem(fibanacciSeq), _NElem(mid.data)); ++i ) {
      mid.data[i] = fibanacciSeq[i];
   }
}

bool IsSpecialMachineId(const net::MACHINE_ID& mid)
{
   int i;
   for ( i = 0; i < min(_NElem(fibanacciSeq), _NElem(mid.data)); ++i ) {
      if ( mid.data[i] != fibanacciSeq[i] ) {
         return false;
      }
   }
   for ( ; i < _NElem(mid.data); ++i ) {
      if ( mid.data[i] != 0 ) {
         return false;
      }
   }
   return true;
}

}

//
//#include "fileio.h"
//
//binlog::BINARY_EVENT_LOG *binLog = NULL;
//
//namespace binlog {
//
///*************************************************************
//* BINARY_EVENT_LOG_READER::Load()
//* 
//*************************************************************/
//void BINARY_EVENT_LOG_READER::Load(class fioFILE *file)
//{
//   int   len;
//   char  buf[2048];
//
//   dsVECTOR<LOG_EVENT> clientEvents[16];
//
//   events.Clear();
//   dict.Clear();
//
//   while (!file->IsEOF()) {
//
//      BYTE type;
//      file->Read(&type);
//
//      if (type == BT_ID_SYNC) {
//         file->ReadPacked(&len);
//         file->Read((BYTE*)buf, len);
//         buf[len] = 0;
//         dict.PushBack(dsSTRING(buf));
//         continue;
//      }
//
//      TimeStampDiffLong timeRef;
//      file->Read(&timeRef);
//
//      BYTE clientId;
//      file->Read(&clientId);
//      BYTE remoteClientId = clientId >> 4;
//      clientId = clientId & 0xf;
//
//      LOG_EVENT &ev = clientEvents[clientId].PushBack();
//      ev.type           = (BLOCK_TYPE)type;
//      ev.timeRef        = timeRef;
//      ev.clientId       = clientId;
//      ev.remoteClientId = remoteClientId;;
//
//      file->ReadPacked(&ev.id);
//
//      switch (type) {
//      case BT_ID_SYNC:
//         break;
//
//      case BT_TIMESTAMP:
//         ASSERT(FALSE);
//         break;
//
//      case BT_MSG_NO_PARAM:
//         file->ReadPacked(&ev.msgId);
//         break;
//
//      case BT_MSG_I:
//         file->ReadPacked(&ev.msgId);
//         file->ReadPacked(&ev.val1);
//         break;
//
//      case BT_MSG_I_I:
//         file->ReadPacked(&ev.msgId);
//         file->ReadPacked(&ev.val1);
//         file->ReadPacked(&ev.val2);
//         break;
//
//      case BT_MSG_I_I_I:
//         file->ReadPacked(&ev.msgId);
//         file->ReadPacked(&ev.val1);
//         file->ReadPacked(&ev.val2);
//         file->ReadPacked(&ev.val3);
//         break;
//
//      case BT_MSG_S:
//         file->ReadPacked(&ev.msgId);
//         file->ReadPacked(&len);
//         file->Read((BYTE*)buf, len); buf[len] = 0;
//         ev.str = buf;
//         break;
//
//      case BT_MSG_S_I:
//         file->ReadPacked(&ev.msgId);
//         file->ReadPacked(&len);
//         file->Read((BYTE*)buf, len); buf[len] = 0;
//         ev.str = buf;
//         file->ReadPacked(&ev.val1);
//         break;
//
//      case BT_MSG_S_I_I:
//         file->ReadPacked(&ev.msgId);
//         file->ReadPacked(&len);
//         file->Read((BYTE*)buf, len); buf[len] = 0;
//         ev.str = buf;
//         file->ReadPacked(&ev.val1);
//         file->ReadPacked(&ev.val2);
//         break;
//
//      case BT_SAMPLE:
//      case BT_STAT:
//         file->ReadPacked(&ev.val1);
//         break;
//
//      case BT_DATA:
//         file->ReadPacked(&len);
//         ev.data.Resize(len);
//         file->Read(&ev.data[0], len);
//         break;
//
//      default:
//         ASSERT(FALSE);
//      }
//   }
//
//   int totalEvents = 0;
//
//   for (int i = 0; i < 16; i++) {
//      totalEvents += clientEvents[i].Length();
//   }
//
//   events.Reserve(totalEvents);
//
//   TimeStampDiffLong minTimeRef;
//   int pos[16] = {0};
//
//   while (TRUE) {
//
//      minTimeRef = -1;
//
//      for (int i = 0; i < 16; i++) {
//         if (pos[i] >= clientEvents[i].Length()) {
//            continue;
//         }
//
//         LOG_EVENT &ev = clientEvents[i][pos[i]];
//
//         if (minTimeRef < 0 || ev.timeRef < minTimeRef) {
//            minTimeRef = ev.timeRef;
//         }
//      }
//
//      if (minTimeRef < 0) {
//         break;
//      }
//
//      for (int i = 0; i < 16; i++) {
//
//         while (TRUE) {
//            if (pos[i] >= clientEvents[i].Length()) {
//               break;
//            }
//
//            LOG_EVENT &ev = clientEvents[i][pos[i]];
//
//            if (ev.timeRef == minTimeRef) {
//               LOG_EVENT &ev_ = events.PushBack();
//               ev_ = ev;
//               pos[i]++;
//            } else {
//               break;
//            }
//         }
//      }
//   }
//
//   return;
//}
//
///*************************************************************
//* BINARY_EVENT_LOG_READER::GetNumEvents()
//* 
//*************************************************************/
//int BINARY_EVENT_LOG_READER::GetNumEvents(void)
//{
//   return events.Length();
//}
//
///*************************************************************
//* BINARY_EVENT_LOG_READER::GetEvent()
//* 
//*************************************************************/
//const EVENT* BINARY_EVENT_LOG_READER::GetEvent(int index)
//{
//   static char  buf[2048];
//   static EVENT ev;
//
//   LOG_EVENT &logEvent = events[index];
//
//   ev.clientId       = logEvent.clientId;
//   ev.remoteClientId = logEvent.remoteClientId;
//   ev.id             = logEvent.id;
//   ev.timeRef        = logEvent.timeRef;
//
//   switch (logEvent.type) {
//   case BT_MSG_NO_PARAM:
//   case BT_MSG_I:
//   case BT_MSG_I_I:
//   case BT_MSG_I_I_I:
//   case BT_MSG_S:
//   case BT_MSG_S_I:
//   case BT_MSG_S_I_I:
//      ev.type     = EVENT_MESSAGE;
//      switch (logEvent.type) {
//      case BT_MSG_NO_PARAM: strcpy(buf, dict[logEvent.msgId].CStr());                                               break;
//      case BT_MSG_I:        sprintf(buf, dict[logEvent.msgId].CStr(), logEvent.val1);                               break;
//      case BT_MSG_I_I:      sprintf(buf, dict[logEvent.msgId].CStr(), logEvent.val1, logEvent.val2);                break;
//      case BT_MSG_I_I_I:    sprintf(buf, dict[logEvent.msgId].CStr(), logEvent.val1, logEvent.val2, logEvent.val3); break;
//      case BT_MSG_S:        sprintf(buf, dict[logEvent.msgId].CStr(), logEvent.str.CStr());                                break;
//      case BT_MSG_S_I:      sprintf(buf, dict[logEvent.msgId].CStr(), logEvent.str.CStr(), logEvent.val1);                 break;
//      case BT_MSG_S_I_I:    sprintf(buf, dict[logEvent.msgId].CStr(), logEvent.str.CStr(), logEvent.val1, logEvent.val2);  break;
//      }
//      ev.comment  = buf;
//      break;
//
//   case BT_SAMPLE:
//      ev.type  = EVENT_SAMPLE;
//      ev.val   = logEvent.val1;
//      break;
//
//   case BT_STAT:
//      ev.type  = EVENT_STAT;
//      ev.val   = logEvent.val1;
//      break;
//
//   case BT_DATA:
//      ev.type     = EVENT_STAT;
//      ev.data     = &logEvent.data[0];
//      ev.length   = logEvent.data.Length();
//      break;
//
//   default:
//      ASSERT(FALSE);
//   }
//
//   return &ev; 
//}
//
///*************************************************************
//* BINARY_EVENT_LOG_READER::GetTimeFrameLength()
//* 
//*************************************************************/
//int BINARY_EVENT_LOG_READER::GetTimeFrameLength(void)
//{
//   if (events.Length() == 0) {
//      return 0;
//   }
//
//   return events[events.Length() - 1].timeRef;
//}
//
///*************************************************************
//* BINARY_EVENT_LOG_READER::FindEvent()
//* 
//*************************************************************/
//int BINARY_EVENT_LOG_READER::FindEvent(int timeRef)
//{
//   return events.FindSorted(timeRef, CMP());
//}
//
//}
